package com.ifonly.mvc.helpers;

import com.google.common.base.Strings;
import com.ifonly.mvc.ActionKey;
import com.ifonly.mvc.ActionMapper;
import com.ifonly.mvc.Controller;
import com.ifonly.utils.ReflectUtils;
import com.ifonly.utils.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * @author <a href="mailto:ifonlymaster@163.com">ifonly</a>
 * @version 1.0 2015-12-15 11:03
 * @since JDK 1.6
 */
public class ActionMappingHelper {

    /**
     * 将一个 Controller 类映射成 Action
     *
     * 映射规则:
     *  1. Controller中所有的 public 方法才会进行映射
     *
     *  2. packagePart + classPart + methodPart 3个部分才会拼接成最终的 actionUrl
     *
     *  3. 首先获取 methodPart 部分
     *     3.1 如果 Method 的 ActionKey 进行了配置, 并且 是以 "/" 开头, 则 actionUrl 就为 ActionKey,
     *      设置 packagePart = "", classPart = "", methodPart="/xxx" -> actionUrl = "/xxx"
     *
     *     3.2 如果 Method 的 ActionKey 进行了配置, 并且 是不以 "/" 开头, 则 methodPart 就为 ActionKey 的值
     *      设置 methodPart = "xxx"
     *
     *     3.3 如果 Method 的 ActionKey 没有配置, methodPart 为 方法名
     *
     * 4. 获取 classPart 部分
     *    4.1 如果 Class 的 ActionKey 进行了配置, 并且 是以 "/" 开头 则 actionUrl 就为 ActionKey + methodPart,
     *      设置 packagePart = "", classPart = "/xxx", methodPart="xxx" -> actionUrl = "/xxx/xxx"
     *
     *    4.2 如果 Class 的 ActionKey 进行了配置, 并且 是不以 "/" 开头 则 actionUrl 就为 classPart 就为 ActionKey 的值,
     *      设置 classPart = "xxx", methodPart="xxx"
     *
     *    4.3 如果 Class 的 ActionKey 没有配置, classPart 为 类名 - Controller
     *
     * 5. 获取 packagePart 部分
     *
     *   4.1 首选获取到 Class 包名, 如果没有包名, packagePart = ""
     *   4.2 检测包名是否有 controller 或 controllers, 截取[controller 或 controllers]后面的部分 如
     *       com.controller.admin.system --> "admin/system"
     *       com.controller --> ""
     *
     *   4.3 没有 [controller 或 controllers], 则获取到上级包名, 如
     *      com.admin.system --> "system"
     *      com.admin --> "admin"
     *
     * 6. viewDir: 如果 actionUrl 为 "/xxx" --> "/"; 如果为 "/xxx/xxx" 这样多级 则去除最后一级 --> "/xxx"
     *
     * @param controllerClass Controller类对象
     * @param actionMapper ActionMapper容器
     * @param checkExist 是否检查存在
     */
    public static void controllerActionMapping(Class<? extends Controller> controllerClass,
                                               ActionMapper actionMapper, boolean checkExist) {
        Method[] methods = ReflectUtils.publicMethods(controllerClass, true);

        if (methods != null && methods.length > 0) {
            String packagePart = extractPackagePart(controllerClass);
            String classPart = extractClassPart(controllerClass);
            for (Method method : methods) {
                String methodPart = extractMethodPart(method);

                String actionUrl = createActionKey(packagePart, classPart, methodPart);
                String viewDir = actionUrl;
                int length = actionUrl.length();
                if (length > 1 && actionUrl.endsWith("/")) {
                    actionUrl = actionUrl.substring(0, length -1);
                }

                if (checkExist && actionMapper.allMappingUrl().contains(actionUrl)) {
                    continue;
                }

                actionMapper.addAction(actionUrl,
                        extractViewDir(viewDir),
                        controllerClass.getName(),
                        controllerClass,
                        method.getName(), method);
            }
        }
    }

    private static String extractViewDir(String actionUrl) {
        if (actionUrl.lastIndexOf("/") == 0) {
            return "/";
        }
        return actionUrl.substring(0, actionUrl.lastIndexOf("/"));
    }

    private static String createActionKey(String packagePart, String classPart, String methodPart) {
        if (methodPart.startsWith("/")) {
            return methodPart;
        }
        if ("".equals(classPart) || "/".equals(classPart)) {
            return "/" + methodPart;
        }
        if (classPart.startsWith("/")) {
            return classPart + "/" + methodPart;
        }
        if ("".equals(packagePart) || "/".equals(packagePart)) {
            return "/" + classPart + "/" + methodPart;
        }
        return "/" + packagePart + "/" + classPart + "/" + methodPart;
    }

    private static String extractMethodPart(Method method) {
        ActionKey actionKey = method.getAnnotation(ActionKey.class);
        String methodPart;
        if (actionKey != null) {
            methodPart = (String) ReflectUtils.annotationInvoke(actionKey, "value");
        } else {
            methodPart = method.getName();
        }
        return methodPart;
    }

    private static String extractClassPart(Class<? extends Controller> clazz) {
        Annotation actionKey = clazz.getAnnotation(ActionKey.class);
        String classPart;
        if (actionKey != null) {
            classPart = (String) ReflectUtils.annotationInvoke(actionKey, "value");
        } else {
            String classSimpleName = clazz.getSimpleName();
            if (classSimpleName.equalsIgnoreCase("controller")) {
                classPart = classSimpleName.toLowerCase();
            } else {
                if (classSimpleName.endsWith("Controller")) {
                    classPart = StringUtils.firstCharToLowerCase(classSimpleName.substring(0
                            , classSimpleName.lastIndexOf("Controller")));
                } else {
                    classPart = StringUtils.firstCharToLowerCase(classSimpleName);
                }
            }
        }
        return classPart;
    }


    private static String extractPackagePart(Class<? extends Controller> clazz) {
        Package pkg = clazz.getPackage();
        if (pkg == null) {
            return null;
        }
        String packageName = pkg.getName();
        if (Strings.isNullOrEmpty(packageName)) {
            return "";
        }

        int start = packageName.indexOf("controllers");
        if (start > -1) {
            packageName = packageName.substring(start + 11); //11 - length of word 'controllers'
            if (packageName.startsWith(".")) {
                packageName = packageName.replaceFirst("[.]", "");
            }
            return packageName.replaceAll("[.]", "/");
        }

        start = packageName.indexOf("controller");
        if (start > -1) {
            packageName = packageName.substring(start + 10); //10 - length of word 'controller'
            if (packageName.startsWith(".")) {
                packageName = packageName.replaceFirst("[.]", "");
            }

            return packageName.replaceAll("[.]", "/");
        }

        String[] arr = packageName.split("[.]");
        return arr[arr.length - 1];
    }

}
